package com.rengwuxian.coursecoroutines._2_structured_concurrency

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlin.coroutines.EmptyCoroutineContext
import kotlin.time.measureTime

/**
 * 协程的结构化取消
 * Job.cancel()、父子协程、协程的结构化结束、协程的交互式取消
 *
 * 父协程的取消，会带着所有子协程一块取消。
 * 父协程的取消，子协程能拒绝吗？
 *
 * 每个Job的cancel里面都包含了对它的每个子Job的cancel()的调用，这是一个链。直到整个协程树都取消。
 * 调用cancel()，就是设置标记，将isActive变成false。
 * 协程的取消本质：
 * 是一个交互式流程，从外部调用Job.cancel()，然后协程内部取检查Job.isActive的改变，
 * 如果是false，就真正取消我自己。真正取消：结束代码的执行，并且抛出一个CancellationException
 *
 * 抛异常的过程，每个协程是独立的，互相之间不影响。每个协程怎么抛异常，什么时候抛，抛不抛，都是不确定的。
 *
 * 附：如果没有调用cancel(),一个协程内部抛出了CancellationException，和调用cancel()的效果一样。
 *     如此：协程可以自我取消。
 *
 * 故而取消协程的方式有两种：
 * 1.外部调用cancel()
 * 2.内部抛出CancellationException
 *
 * Job的取消过程包括三件事：
 * 1.Job的isActive从true变成false
 * 2.调用所有子协程的cancel()
 * 3.在自己的代码块里检查isActive的状态，并在发现它变成false后抛异常。
 *
 * 父协程的取消，子协程能拒绝吗？ -- 只能一点点，知道做法后，就是不能拒绝了。
 * 上面1.做不到
 * 上面2.做不到
 * 上面3.可以做一点点，垂死挣扎一下，将程序执行完。
 * 怎么挣扎呢？所有遇到suspend函数的地方都要try catch住。
 * 强行拒绝的做法，很有风险，打乱程序的节奏，不推荐。而且会拖住父协程无法取消。（协程的结构化结束）
 * 例子：
 *     val parentJob = scope.launch {
 *     val childJob = launch {
 *       println("Child job started")
 * //      delay(3000)
 *       Thread.sleep(3000)
 *       println("Child job finished")
 *     }
 *   }
 *   delay(1000)
 *   parentJob.cancel()
 *   measureTime { parentJob.join() }.also { println("Duration: $it") }
 *
 * 打印如下：
 * Child job started
 * Child job finished
 * Duration: 1.975026543s
 *
 * 可以看到父协程等待子协程结束后，才走到join。
 * 换成delay()配合取消，则父协程会立即结束。
 */
fun main() = runBlocking<Unit> {
  val scope = CoroutineScope(EmptyCoroutineContext)
  val parentJob = scope.launch {
    val childJob = launch {
      println("Child job started")
//      delay(3000)
      Thread.sleep(3000)
      println("Child job finished")
    }
  }
  delay(1000)
  parentJob.cancel()
  measureTime { parentJob.join() }.also { println("Duration: $it") }
  delay(10000)
}